{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "# SQL Database\n",
    "\n",
    "::: {.callout-note}\n",
    "The `SQLDatabase` adapter utility is a wrapper around a database connection.\n",
    "\n",
    "For talking to SQL databases, it uses the [SQLAlchemy] Core API .\n",
    ":::\n",
    "\n",
    "\n",
    "This notebook shows how to use the utility to access an SQLite database.\n",
    "It uses the example [Chinook Database], and demonstrates those features:\n",
    "\n",
    "- Query using SQL\n",
    "- Query using SQLAlchemy selectable\n",
    "- Fetch modes `cursor`, `all`, and `one`\n",
    "- Bind query parameters\n",
    "\n",
    "[Chinook Database]: https://github.com/lerocha/chinook-database\n",
    "[SQLAlchemy]: https://www.sqlalchemy.org/\n",
    "\n",
    "\n",
    "You can use the `Tool` or `@tool` decorator to create a tool from this utility.\n",
    "\n",
    "\n",
    "::: {.callout-caution}\n",
    "If creating a tool from the SQLDatbase utility and combining it with an LLM or exposing it to an end user\n",
    "remember to follow good security practices.\n",
    "\n",
    "See security information: https://python.langchain.com/docs/security\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true,
    "jupyter": {
     "outputs_hidden": true
    }
   },
   "outputs": [],
   "source": [
    "!wget 'https://github.com/lerocha/chinook-database/releases/download/v1.4.2/Chinook_Sqlite.sql'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "1|AC/DC\r\n",
      "2|Accept\r\n",
      "3|Aerosmith\r\n",
      "4|Alanis Morissette\r\n",
      "5|Alice In Chains\r\n",
      "6|Antônio Carlos Jobim\r\n",
      "7|Apocalyptica\r\n",
      "8|Audioslave\r\n",
      "9|BackBeat\r\n",
      "10|Billy Cobham\r\n",
      "11|Black Label Society\r\n",
      "12|Black Sabbath\r\n"
     ]
    }
   ],
   "source": [
    "!sqlite3 -bail -cmd '.read Chinook_Sqlite.sql' -cmd 'SELECT * FROM Artist LIMIT 12;' -cmd '.quit'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "!sqlite3 -bail -cmd '.read Chinook_Sqlite.sql' -cmd '.save Chinook.db' -cmd '.quit'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Initialize Database"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "from pprint import pprint\n",
    "\n",
    "import sqlalchemy as sa\n",
    "from langchain_community.utilities import SQLDatabase\n",
    "\n",
    "db = SQLDatabase.from_uri(\"sqlite:///Chinook.db\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Query as cursor\n",
    "\n",
    "The fetch mode `cursor` returns results as SQLAlchemy's\n",
    "`CursorResult` instance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'sqlalchemy.engine.cursor.CursorResult'>\n",
      "[{'ArtistId': 1, 'Name': 'AC/DC'},\n",
      " {'ArtistId': 2, 'Name': 'Accept'},\n",
      " {'ArtistId': 3, 'Name': 'Aerosmith'},\n",
      " {'ArtistId': 4, 'Name': 'Alanis Morissette'},\n",
      " {'ArtistId': 5, 'Name': 'Alice In Chains'},\n",
      " {'ArtistId': 6, 'Name': 'Antônio Carlos Jobim'},\n",
      " {'ArtistId': 7, 'Name': 'Apocalyptica'},\n",
      " {'ArtistId': 8, 'Name': 'Audioslave'},\n",
      " {'ArtistId': 9, 'Name': 'BackBeat'},\n",
      " {'ArtistId': 10, 'Name': 'Billy Cobham'},\n",
      " {'ArtistId': 11, 'Name': 'Black Label Society'},\n",
      " {'ArtistId': 12, 'Name': 'Black Sabbath'}]\n"
     ]
    }
   ],
   "source": [
    "result = db.run(\"SELECT * FROM Artist LIMIT 12;\", fetch=\"cursor\")\n",
    "print(type(result))\n",
    "pprint(list(result.mappings()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Query as string payload\n",
    "\n",
    "The fetch modes `all` and `one` return results in string format."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'str'>\n",
      "[(1, 'AC/DC'), (2, 'Accept'), (3, 'Aerosmith'), (4, 'Alanis Morissette'), (5, 'Alice In Chains'), (6, 'Antônio Carlos Jobim'), (7, 'Apocalyptica'), (8, 'Audioslave'), (9, 'BackBeat'), (10, 'Billy Cobham'), (11, 'Black Label Society'), (12, 'Black Sabbath')]\n"
     ]
    }
   ],
   "source": [
    "result = db.run(\"SELECT * FROM Artist LIMIT 12;\", fetch=\"all\")\n",
    "print(type(result))\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class 'str'>\n",
      "[(1, 'AC/DC')]\n"
     ]
    }
   ],
   "source": [
    "result = db.run(\"SELECT * FROM Artist LIMIT 12;\", fetch=\"one\")\n",
    "print(type(result))\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Query with parameters\n",
    "\n",
    "In order to bind query parameters, use the optional `parameters` argument."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'ArtistId': 35, 'Name': 'Pedro Luís & A Parede'},\n",
      " {'ArtistId': 115, 'Name': 'Page & Plant'},\n",
      " {'ArtistId': 116, 'Name': 'Passengers'},\n",
      " {'ArtistId': 117, 'Name': \"Paul D'Ianno\"},\n",
      " {'ArtistId': 118, 'Name': 'Pearl Jam'},\n",
      " {'ArtistId': 119, 'Name': 'Peter Tosh'},\n",
      " {'ArtistId': 120, 'Name': 'Pink Floyd'},\n",
      " {'ArtistId': 121, 'Name': 'Planet Hemp'},\n",
      " {'ArtistId': 186, 'Name': 'Pedro Luís E A Parede'},\n",
      " {'ArtistId': 256, 'Name': 'Philharmonia Orchestra & Sir Neville Marriner'},\n",
      " {'ArtistId': 275, 'Name': 'Philip Glass Ensemble'}]\n"
     ]
    }
   ],
   "source": [
    "result = db.run(\n",
    "    \"SELECT * FROM Artist WHERE Name LIKE :search;\",\n",
    "    parameters={\"search\": \"p%\"},\n",
    "    fetch=\"cursor\",\n",
    ")\n",
    "pprint(list(result.mappings()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Query with SQLAlchemy selectable\n",
    "\n",
    "Other than plain-text SQL statements, the adapter also accepts SQLAlchemy selectables."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[{'ArtistId': 35, 'Name': 'Pedro Luís & A Parede'},\n",
      " {'ArtistId': 115, 'Name': 'Page & Plant'},\n",
      " {'ArtistId': 116, 'Name': 'Passengers'},\n",
      " {'ArtistId': 117, 'Name': \"Paul D'Ianno\"},\n",
      " {'ArtistId': 118, 'Name': 'Pearl Jam'},\n",
      " {'ArtistId': 119, 'Name': 'Peter Tosh'},\n",
      " {'ArtistId': 120, 'Name': 'Pink Floyd'},\n",
      " {'ArtistId': 121, 'Name': 'Planet Hemp'},\n",
      " {'ArtistId': 186, 'Name': 'Pedro Luís E A Parede'},\n",
      " {'ArtistId': 256, 'Name': 'Philharmonia Orchestra & Sir Neville Marriner'},\n",
      " {'ArtistId': 275, 'Name': 'Philip Glass Ensemble'}]\n"
     ]
    }
   ],
   "source": [
    "# In order to build a selectable on SA's Core API, you need a table definition.\n",
    "metadata = sa.MetaData()\n",
    "artist = sa.Table(\n",
    "    \"Artist\",\n",
    "    metadata,\n",
    "    sa.Column(\"ArtistId\", sa.INTEGER, primary_key=True),\n",
    "    sa.Column(\"Name\", sa.TEXT),\n",
    ")\n",
    "\n",
    "# Build a selectable with the same semantics of the recent query.\n",
    "query = sa.select(artist).where(artist.c.Name.like(\"p%\"))\n",
    "result = db.run(query, fetch=\"cursor\")\n",
    "pprint(list(result.mappings()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "source": [
    "## Query with execution options\n",
    "\n",
    "It is possible to augment the statement invocation with custom execution options.\n",
    "For example, when applying a schema name translation, subsequent statements will\n",
    "fail, because they try to hit a non-existing table."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [],
   "source": [
    "query = sa.select(artist).where(artist.c.Name.like(\"p%\"))\n",
    "db.run(query, fetch=\"cursor\", execution_options={\"schema_translate_map\": {None: \"bar\"}})"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
